/**
 * @file MainWindow.cpp
 *
 * Implementation of class GlWidget.
 *
 * @author M. Caputo
 * @date 2010-02-25
 *
 * @todo comments!
 */

#include "GlWidget.h"
#include "WireframeMode.h"
#include "DotMode.h"
#include "FlatShadedMode.h"
#include "ColoredMode.h"

/**
 * @brief Constructor of class GlWidget.
 * The class inherits QGlWidget (public).
 *
 * @param[in] parent Parent widget (default = 0).
 */
GlWidget::GlWidget(QWidget *parent): QGLWidget(parent)
{
	scene = 0;
	
	renderModes.append(new WireframeMode());
	renderModes.append(new DotMode());
	renderModes.append(new FlatShadedMode());
	renderModes.append(new ColoredMode());
	activeMode = 0;

	setFocusPolicy(Qt::ClickFocus);
	
	resetView();
}

/**
 * @brief Destructor of class GlWidget
 */
GlWidget::~GlWidget()
{
	for(int i=0; i<renderModes.size(); i++) {
		delete renderModes[i];
	}
}

void GlWidget::initializeGL()
{
	renderModes[activeMode]->setSettings();
}

/**
 * @brief Function that is called whenever the size of the widget has to be readjusted
 */
void GlWidget::resizeGL(int w, int h)
{
	int side = qMin(w, h);
	glViewport((w - side) / 2, (h - side) / 2, side, side);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-1.0, +1.0, -1.0, +1.0, 1.5, 20.0);

	glTranslatef(0.0, 0.0, -3.0);

	glMatrixMode(GL_MODELVIEW);
}

/**
 *	@brief central function that draws the Object, axes and planes.
 */
void GlWidget::paintGL()
{
	glClearColor(
		bgColor.redF(),
		bgColor.greenF(),
		bgColor.blueF(),
		bgColor.alphaF()
	);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (isBeingReset) {
		glLoadIdentity();
		isBeingReset = false;
	}

	if (mustScale)
	{
		glScalef(scale, scale, scale);
		mustScale = false;
	}
	
	if (mustTrans) {
		glTranslatef(xTrans, yTrans, zTrans);
		mustTrans = false;
	}
	
	if (!xDown && !yDown) {
		glRotatef(curRot, 0.0, 1.0, 0.0);
	} else if (!xDown && yDown) {
		glRotatef(curRot, 1.0, 0.0, 0.0);
	} else if (xDown && !yDown) {
		glRotatef(curRot, 0.0, 0.0, 1.0);
	}
	curRot = 0.0;
	
	if (showAxes) drawAxes();
	
	int lines = 5;
	float stepSize = 0.2;
	QColor lineColor(100, 100, 100); // grey
	
	if (showPlanes[0]) drawXzPlane(lines, stepSize, lineColor);
	if (showPlanes[1]) drawXyPlane(lines, stepSize, lineColor);
	if (showPlanes[2]) drawYzPlane(lines, stepSize, lineColor);
	
	if (scene) {
		renderModes[activeMode]->draw(scene, color);
	}
}

/**
 *	@brief updates lastPos
 */
void GlWidget::mousePressEvent(QMouseEvent *event)
{
	lastPos = event->pos();
}

/**
 *	@brief processes mouseMoveEvents. Depending on which mouse and/or keyboard
 *		buttons are pressed, the variables, paintGL uses to manipulate the
 *		object, are changed.
 *
 */
void GlWidget::mouseMoveEvent(QMouseEvent *event)
{
	int dx = event->x() - lastPos.x();
	int dy = event->y() - lastPos.y();

	// concerning rotatef
	if ((event->buttons() & Qt::LeftButton) && !yDown && !xDown) {
		curRot = static_cast<float>(dx)/2.0;
	} else if ((event->buttons() & Qt::LeftButton) && yDown && !xDown) {
		curRot = static_cast<float>(dy)/2.0;
	} else if ((event->buttons() & Qt::LeftButton) && !yDown && xDown) {
		curRot = static_cast<float>(dy)/2.0;
	}

	// concerning translatef
	else if ((event->buttons() & Qt::RightButton) && !yDown && !xDown) {
		xTrans = (static_cast<double>(dx)/200.0);
		yTrans = -(static_cast<double>(dy)/200.0);
		zTrans = 0.0;
		mustTrans = true;
	} else if ((event->buttons() & Qt::RightButton) && yDown && !xDown) {
		xTrans = 0.0;
		yTrans = 0.0;
		zTrans = (static_cast<double>(dy)/200.0);
		mustTrans = true;
	}
	
	lastPos = event->pos();

	// call update to paint the changes we made here
	updateGL();
}

/**
 *	@brief This function is all about zooming. Whenever we scroll with the mouse
 *		wheel, we zoom in/out
 */
void GlWidget::wheelEvent(QWheelEvent *event)
{
	if (event->delta() > 0) {
		scale = 1.1;
		totalScale += 0.1;
	} else {
		if (scale > 0.1) {
			scale = 0.9;
			totalScale -= 0.1;
		}
		else {
			return;
		}
	}

	mustScale = true;

	// call update to paint the changes we made here
	updateGL();
}

void GlWidget::keyPressEvent(QKeyEvent *event)
{
	switch(event->key()) {
		case Qt::Key_Y :
			yDown = true;
			break;
		case Qt::Key_X :
			xDown = true;
			break;
		default :
			QGLWidget::keyPressEvent(event);
	}
}

void GlWidget::keyReleaseEvent(QKeyEvent *event)
{
	switch(event->key()) {
		case Qt::Key_Y :
			if(!event->isAutoRepeat()) {
				yDown = false;
			}
			break;
		case Qt::Key_X :
			if(!event->isAutoRepeat()) {
				xDown = false;
			}
			break;
		default :
			QGLWidget::keyReleaseEvent(event);
	}
}

void GlWidget::drawAxes()
{
	glLineWidth(3.0);
	glBegin( GL_LINES );	
		glColor3f(1, 0, 0);
		glVertex3f(0, 0, 0);
		glVertex3f(1, 0, 0);
		
		glColor3f(0, 1, 0);
		glVertex3f(0, 0, 0);
		glVertex3f(0, 1, 0);

		glColor3f(0, 0, 1);
		glVertex3f(0, 0, 0);
		glVertex3f(0, 0, 1);	
	glEnd();
	glLineWidth(1.0);
}

void GlWidget::drawXzPlane(int lines, float stepSize, const QColor & c)
{
	glColor3f(c.redF(), c.greenF(), c.blueF());
	glBegin( GL_LINES );
	for(int i=-lines; i<=lines; i++) {
		glVertex3f(stepSize*i, 0, -lines*stepSize);
		glVertex3f(stepSize*i, 0, lines*stepSize);
	}
	for(int i=-lines; i<=lines; i++) {
		glVertex3f(-lines*stepSize, 0, stepSize*i);
		glVertex3f(lines*stepSize, 0, stepSize*i);
	}	
	glEnd();
}

void GlWidget::drawXyPlane(int lines, float stepSize, const QColor & c)
{
	glColor3f(c.redF(), c.greenF(), c.blueF());
	glBegin( GL_LINES );
	for(int i=-lines; i<=lines; i++) {
		glVertex3f(stepSize*i, -lines*stepSize, 0);
		glVertex3f(stepSize*i, lines*stepSize, 0);
	}
	for(int i=-lines; i<=lines; i++) {
		glVertex3f(-lines*stepSize, stepSize*i, 0);
		glVertex3f(lines*stepSize, stepSize*i, 0);
	}	
	glEnd();
}

void GlWidget::drawYzPlane(int lines, float stepSize, const QColor & c)
{
	glColor3f(c.redF(), c.greenF(), c.blueF());
	glBegin( GL_LINES );
	for(int i=-lines; i<=lines; i++) {
		glVertex3f(0, stepSize*i, -lines*stepSize);
		glVertex3f(0, stepSize*i, lines*stepSize);
	}
	for(int i=-lines; i<=lines; i++) {
		glVertex3f(0, -lines*stepSize, stepSize*i);
		glVertex3f(0, lines*stepSize, stepSize*i);
	}	
	glEnd();
}

void GlWidget::setScene(IScene *scene)
{
	this->scene = scene;
	resetView();
	updateGL();
}

void GlWidget::setBackgroundColor(const QColor & c)
{
	bgColor = c;
	updateGL();
}

QColor GlWidget::backgroundColor()
{
	return bgColor;
}

void GlWidget::setObjectColor(const QColor & c)
{
	color = c;
	updateGL();
}

QColor GlWidget::objectColor()
{
	return color;
}

void GlWidget::setXzPlane(bool status)
{
	showPlanes[0] = status;
	updateGL();
}

bool GlWidget::xzPlane()
{
	return showPlanes[0];
}

void GlWidget::setXyPlane(bool status)
{
	showPlanes[1] = status;
	updateGL();
}

bool GlWidget::xyPlane()
{
	return showPlanes[1];
}

void GlWidget::setYzPlane(bool status)
{
	showPlanes[2] = status;
	updateGL();
}

bool GlWidget::yzPlane()
{
	return showPlanes[2];
}

void GlWidget::setAxes(bool status)
{
	showAxes = status;
	updateGL();
}

bool GlWidget::axes()
{
	return showAxes;
}

void GlWidget::resetView()
{
	isBeingReset = true;
	yDown = false;
	xDown = false;
	mustScale = false;
	mustTrans = true;

	color = QColor("white");
	bgColor = QColor("black");
	
	showAxes = true;
	
	showPlanes[0] = true;
	showPlanes[1] = false;
	showPlanes[2] = false;

	scale = 1.0;		// just for being proper, not really needed...
	totalScale = 1.0;	//... in contra to that!
	
	curRot = 0.0;
	
	xTrans = 0.0;
	yTrans = 0.0;
	zTrans = 0.0;
	
	updateGL();
}

QStringList GlWidget::listRenderModes()
{
	QStringList list;
	for(int i=0; i<renderModes.size(); i++) {
		list.append(renderModes[i]->name());
	}
	return list;
}

int GlWidget::renderMode()
{
	return activeMode;
}

void GlWidget::setRenderMode(int m)
{
	renderModes[activeMode]->unsetSettings();
	activeMode = m;
	renderModes[activeMode]->setSettings();
	updateGL();
}

QString GlWidget::renderModeName()
{
	return renderModes[activeMode]->name();
}
